using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading;
using System.IO;

namespace Lib
{
    public class ActiveLogReader : IDisposable {
        private readonly string _directory;
        private readonly bool _recursive;
        public static int UpdateInterval = 200;

        public event FileChangedEventHandler FileChanged;
        public event Action<string> FileCreated;
        public event Action<IEnumerable<byte>> DataRetrieved;
        public event Action<string> DataTranslated;

        private readonly List<string> _fileMasks;
        private readonly Timer _updateTimer;
        private readonly List<LogFileInfo> _infos;
        private bool _isDisposed;
        private readonly FileSystemWatcher _watcher;

        public IDataRetriever DataRetriever { get; set; }
        public IDataTranslator DataTranslator { get; set; }
        public IDataProcessor DataProcessor { get; set; }

        public ActiveLogReader(string directory, bool recursive, IEnumerable<string> fileMasks, bool regex)
        {
            _updateTimer = new Timer(CheckFiles);
            _fileMasks = new List<string>();
            _infos = new List<LogFileInfo>();

            _directory = directory;
            _recursive = recursive;

            foreach(string mask in fileMasks)
            {
                if(regex)
                {
                    _fileMasks.Add(mask);
                }
                else
                {
                    _fileMasks.Add(mask.Replace(".", "\\.").Replace('?', '.').Replace("*", ".*?"));
                }
            }

            _watcher = new FileSystemWatcher(_directory)
            {
                IncludeSubdirectories = recursive
            };
            _watcher.Created += OnWatcherCreated;
            _watcher.Deleted += OnWatcherDeleted;

            DataRetriever = new DataRetriever();
            DataTranslator = new EncodingTranslator();
            DataProcessor = null;
        }

        void OnWatcherDeleted(object sender, FileSystemEventArgs e)
        {
            if (!MatchesFileMasks(e.Name)) return;
            _infos.RemoveAll(info => info.FullPath == e.FullPath);
        }

        void OnWatcherCreated(object sender, FileSystemEventArgs e)
        {
            if(!MatchesFileMasks(e.Name)) return;
            LogFileInfo fileInfo = new LogFileInfo(e.FullPath, 0);
            _infos.Add(fileInfo);
            if (FileCreated != null) FileCreated(e.FullPath);
            OnFileChanged(fileInfo);
        }

        ~ActiveLogReader()
        {
            ((IDisposable)this).Dispose();
        }

        public void Start()
        {
            if (FileChanged == null && FileCreated == null && DataTranslated != null && DataRetrieved != null && DataProcessor != null)
                throw new InvalidOperationException("Listen to at least one of the available events, or define a DataProcessor");

            _infos.Clear();
            CollectFiles(_directory);
            _updateTimer.Change(0, UpdateInterval);
            _watcher.EnableRaisingEvents = true;
        }

        private bool MatchesFileMasks(string fileName)
        {
            foreach(string mask in _fileMasks)
            {
                if(Regex.IsMatch(fileName, mask))
                    return true;
            }
            return false;
        }

        private void CollectFiles(string root)
        {
            foreach(string file in Directory.GetFiles(root))
            {
                if(MatchesFileMasks(Path.GetFileName(file)))
                    _infos.Add(new LogFileInfo(file));
            }

            if(!_recursive) return;
            foreach(string directory in Directory.GetDirectories(root))
            {
                CollectFiles(directory);
            }
        }

        public void Stop()
        {
            _updateTimer.Change(-1, Timeout.Infinite);
            _watcher.EnableRaisingEvents = false;
        }

        private void CheckFiles(object state)
        {
            foreach(LogFileInfo logFileInfo in _infos)
            {
                if (logFileInfo.HasChanged)
                    OnFileChanged(logFileInfo);
            }
        }

        private void OnFileChanged(LogFileInfo info)
        {
            if(DataRetriever != null)
            {
                var data = DataRetriever.Retrieve(info.FullPath, info.FileOffset, info.Length);
                if (DataRetrieved != null)
                    DataRetrieved(data);

                if(DataTranslator != null)
                {
                    string translation = DataTranslator.Translate(data);
                    if (DataTranslated != null)
                        DataTranslated(translation);

                    if(DataProcessor != null)
                        DataProcessor.Process(translation);
                }
            }

            if (FileChanged != null)
                FileChanged(info.FullPath, info.FileOffset, info.Length);
        }

        void IDisposable.Dispose()
        {
            if(_isDisposed) return;

            _updateTimer.Dispose();
            _watcher.Dispose();
            _isDisposed = true;
            GC.SuppressFinalize(this);
        }
    }
}